/*
 * $Id: $
 */
package org.rcgwt.client.ui.grid.renderer;

import java.util.Iterator;

import org.rcgwt.client.theme.CssStyle;
import org.rcgwt.client.ui.grid.internal.model.IColumnModel;
import org.rcgwt.client.ui.grid.internal.model.IElementModel;
import org.rcgwt.client.ui.grid.internal.model.IElementModelDelta;
import org.rcgwt.client.ui.grid.internal.model.IGridModel;
import org.rcgwt.client.ui.grid.internal.model.IGroupModel;
import org.rcgwt.client.ui.grid.internal.model.IHeaderModel;
import org.rcgwt.client.ui.grid.internal.model.IRowModel;
import org.rcgwt.client.ui.grid.internal.model.ITitleCellModel;
import org.rcgwt.client.ui.grid.internal.model.IViewModel;
import org.rcgwt.client.ui.grid.internal.model.IViewModelElements;
import org.rcgwt.client.ui.grid.theme.IGridTheme;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.TableColElement;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.Style.Unit;

/**
 * 
 * @author Olivier Oeuillot (latest modification by $Author: $)
 * @version $Revision: $ $Date: $
 */
public class DefaultViewRenderer extends AbstractRenderer<IViewModel> implements
		IViewRenderer {

	private static final int X_SCROLL_MODE = 0x01;
	private static final int Y_SCROLL_MODE = 0x02;

	@Override
	protected CssStyle getCssStyle(IViewRenderContext viewRenderContext,
			IViewModel viewModel) {

		IGridTheme theme = viewRenderContext.getGridRenderContext().getTheme();

		return theme.getViewCssStyle(viewModel);
	}

	protected CssStyle getTableCssStyle(IViewRenderContext viewRenderContext,
			IViewModel viewModel) {
		IGridTheme theme = viewRenderContext.getGridRenderContext().getTheme();

		return theme.getTableStyleName(viewModel);
	}

	@Override
	protected void updateStateMask(IViewRenderContext viewRenderContext,
			Element element, IViewModel viewModel) {
		super.updateStateMask(viewRenderContext, element, viewModel);

		CssStyle tableCssStyle = getTableCssStyle(viewRenderContext, viewModel);
		if (tableCssStyle != null) {
			Element tableElement = viewModel.getViewModelElements()
					.getTableElement();
			if (tableElement != null) {
				tableElement.setClassName(tableCssStyle.getClassName());
			}
		}
	}

	@Override
	public void processDelta(IViewRenderContext viewRenderContext,
			Element element, IElementModelDelta<IViewModel> modelElementDelta) {

		super.processDelta(viewRenderContext, element, modelElementDelta);
	}

	@Override
	public void processSibblingDelta(IViewRenderContext viewRenderContext,
			Element viewElement,
			IElementModelDelta<? extends IElementModel> delta) {

		super.processDelta(viewRenderContext, delta);
	}

	public Element render(IViewRenderContext viewRenderContext, Element parent,
			IViewModel viewModel, int renderedIndex) {

		Document document = parent.getOwnerDocument();

		Element viewElement = createFocusableElement(document);

		Element keysEventsElement = viewElement;
		Element scrollEventElement = null;

		IHeaderModel headerModels[] = viewModel.listHeaderModels();

		if (headerModels == null || headerModels.length < 1) {
			Element tableElement = renderTable(viewRenderContext, viewElement,
					viewModel);

			viewElement.appendChild(tableElement);

			updateContent(viewRenderContext, viewElement, viewModel);

			updateStateMask(viewRenderContext, viewElement, viewModel);

		} else {
			IGridTheme theme = viewRenderContext.getGridRenderContext()
					.getTheme();

			IHeaderModel topHeader = viewModel
					.getHeaderModelByPosition(IHeaderModel.Position.TOP);
			if (topHeader != null) {
				IHeaderRenderer headerRenderer = theme
						.getHeaderRenderer(topHeader);

				Element horizontalHeaderElement = headerRenderer.render(
						viewRenderContext, viewElement, topHeader,
						IViewModel.TOP_HEADER);

				viewElement.appendChild(horizontalHeaderElement);
			}

			Element tableParent = viewElement;

			int viewHints = viewModel.getRenderHints();

			if ((viewHints & (IViewModel.HORIZONTAL_SCROLLBAR | IViewModel.VERTICAL_SCROLLBAR)) > 0) {
				scrollEventElement = document.createDivElement();
				scrollEventElement.setTabIndex(-1);

				CssStyle scrollableTableContainerCssStyle = theme
						.getScrollableTableContainerCssStyle();

				if (scrollableTableContainerCssStyle != null) {
					scrollEventElement
							.setClassName(scrollableTableContainerCssStyle
									.getClassName());
				}

				viewElement.appendChild(scrollEventElement);

				tableParent = scrollEventElement;

				viewModel.getViewModelElements().setScrollableTableContainer(
						scrollEventElement);
			}

			Element tableElement = renderTable(viewRenderContext, tableParent,
					viewModel);
			tableParent.appendChild(tableElement);

			IHeaderModel bottomHeader = viewModel
					.getHeaderModelByPosition(IHeaderModel.Position.BOTTOM);
			if (bottomHeader != null) {
				IHeaderRenderer headerRenderer = theme
						.getHeaderRenderer(bottomHeader);

				Element horizontalHeaderElement = headerRenderer.render(
						viewRenderContext, viewElement, topHeader,
						IViewModel.BOTTOM_HEADER);

				viewElement.appendChild(horizontalHeaderElement);
			}

			update(viewRenderContext, viewElement, viewModel, renderedIndex);
		}

		viewRenderContext.getGridRenderContext().getGridModel().getGridWidget()
				.getGridModelController().installElementEventsListener(
						viewRenderContext, viewModel, keysEventsElement,
						scrollEventElement);

		return viewElement;
	}

	@Override
	public void update(IViewRenderContext viewRenderContext,
			Element viewElement, IViewModel viewModel, int renderedIndex) {
		super.update(viewRenderContext, viewElement, viewModel, renderedIndex);

		viewModel.getViewModelElements().setViewElement(viewElement);
	}

	public void updateLayout(IViewRenderContext viewRenderContext,
			IViewModel viewModel, Element viewElement, int innerViewWidth,
			int innerViewHeight, int totalColumnWidth, int[] columnWidths) {

		IGridModel gridModel = viewModel.getGridModel();

		IViewModelElements viewModelElements = viewModel.getViewModelElements();

		IGridTheme gridTheme = viewRenderContext.getGridRenderContext()
				.getTheme();

		Element scrollableTableContainer = viewModelElements
				.getScrollableTableContainer();

		viewModelElements.getTableElement().getStyle().setPropertyPx(
				Styles.WIDTH, totalColumnWidth);

		IColumnModel columnModels[] = gridModel.listColumnModels();
		int columnModelIndex = 0;
		for (IColumnModel columnModel : columnModels) {

			Element col = viewModelElements.getColumnElement(columnModel);
			if (col == null) {
				columnModelIndex++;
				continue;
			}

			TableColElement.as(col).setWidth(
					String.valueOf(columnWidths[columnModelIndex++]));
		}

		if (scrollableTableContainer != null) {
			int w = innerViewWidth;
			int h = innerViewHeight;

			IHeaderModel headerModels[] = viewModel.listHeaderModels();

			for (IHeaderModel headerModel : headerModels) {
				IHeaderRenderer headerRenderer;
				Element headerElement;

				headerRenderer = gridTheme.getHeaderRenderer(headerModel);
				assert headerRenderer != null : "Header renderer is null !";

				headerElement = viewModelElements.getHeaderElement(headerModel);
				if (headerElement == null) {
					continue;
				}

				int headerSize = headerRenderer.getSize(viewRenderContext,
						headerModel, headerElement);
				int headerSeparatorSize = gridTheme
						.getHeaderSeparatorSize(headerModel);

				if (headerModel.getOrientation() == IHeaderModel.Orientation.VERTICAL) {
					w -= headerSize;
					w -= headerSeparatorSize;

					headerRenderer.updateLayout(viewRenderContext, headerModel,
							headerElement, headerSize, innerViewHeight,
							totalColumnWidth, columnWidths);

				} else {
					h -= headerSize;
					h -= headerSeparatorSize;

					headerRenderer.updateLayout(viewRenderContext, headerModel,
							headerElement, innerViewWidth, headerSize,
							totalColumnWidth, columnWidths);
				}

			}

			scrollableTableContainer.getStyle().setPropertyPx(Styles.WIDTH, w);

			scrollableTableContainer.getStyle().setPropertyPx(Styles.HEIGHT, h);
		}
	}

	protected Element renderTable(IViewRenderContext viewRenderContext,
			Element parent, IViewModel viewModel) {

		IGridTheme gridTheme = viewRenderContext.getGridRenderContext()
				.getTheme();

		Document document = parent.getOwnerDocument();

		TableElement tableElement = document.createTableElement();
		tableElement.setCellPadding(gridTheme.getDataTableCellPadding());
		tableElement.setCellSpacing(gridTheme.getDataTableCellSpacing());

		int margins[] = gridTheme.getTableMargins();
		Style tableStyle = tableElement.getStyle();
		tableStyle.setMarginTop(margins[0], Unit.PX);
		tableStyle.setMarginRight(margins[1], Unit.PX);
		tableStyle.setMarginBottom(margins[2], Unit.PX);
		tableStyle.setMarginLeft(margins[3], Unit.PX);

		viewModel.getViewModelElements().setTableElement(tableElement);

		return tableElement;
	}

	@SuppressWarnings("boxing")
	@Override
	protected void updateContent(IViewRenderContext viewRenderContext,
			Element viewElement, IViewModel viewModel) {

		IViewModelElements viewModelElements = viewModel.getViewModelElements();

		IGridRenderContext gridRenderContext = viewRenderContext
				.getGridRenderContext();

		IGridModel gridModel = viewModel.getGridModel();

		IGridTheme theme = gridRenderContext.getTheme();

		viewModelElements.clearGroupAndColumnsElements(viewModel, viewElement);

		TableElement table = TableElement.as(viewModelElements
				.getTableElement());

		NodeList<Node> nodes = null;
		int childrenCount = 0;

		if (table.hasChildNodes()) {
			nodes = table.getChildNodes();
			childrenCount = nodes.getLength();
		}

		int childrenIndex = 0;

		int renderedIndex = 0;
		Element columnInsertElement = null;

		int columnModelIndexes[] = gridRenderContext.getColumnModelIndexes();
		IColumnModel columnModels[] = gridModel.listColumnModels();

		Document document = viewElement.getOwnerDocument();

		for (int i = 0; i < columnModelIndexes.length; i++) {
			IColumnModel columnModel = columnModels[columnModelIndexes[i]];

			TableColElement colElement = null;

			for (; columnInsertElement == null && childrenIndex < childrenCount;) {
				Node child = nodes.getItem(childrenIndex);

				if (child.getNodeType() != Node.ELEMENT_NODE) {
					table.removeChild(child);
					continue;
				}

				childrenIndex++;

				Element element = Element.as(child);

				if ("col".equalsIgnoreCase(element.getTagName())) {
					colElement = TableColElement.as(element);
					break;
				}

				columnInsertElement = element;
			}

			if (colElement == null) {
				colElement = document.createColElement();

				table.insertBefore(colElement, columnInsertElement);
			}

			viewModelElements.setColumnElement(columnModel, colElement,
					renderedIndex++);
		}

		Iterator<Integer> groupModelIndexIterator = gridModel.getGridWidget()
				.getGroupDataModel().getGroupModelIndexes(0, -1);

		IGroupModel groupModels[] = gridModel.listGroupModels();

		renderedIndex = 0;
		for (; groupModelIndexIterator.hasNext();) {
			int groupModelIndex = groupModelIndexIterator.next();

			IGroupModel groupModel = groupModels[groupModelIndex];

			IGroupRenderer groupRenderer = theme.getGroupRenderer(groupModel);

			Element groupElement = null;

			for (; childrenIndex < childrenCount;) {
				Node child = nodes.getItem(childrenIndex);

				if (child.getNodeType() != Node.ELEMENT_NODE) {
					table.removeChild(child);
					continue;
				}

				childrenIndex++;

				groupElement = Element.as(child);
			}

			if (groupElement != null) {
				groupRenderer.update(viewRenderContext, groupElement,
						groupModel, renderedIndex);

				continue;
			}

			groupElement = groupRenderer.render(viewRenderContext, table,
					groupModel, renderedIndex++);

			table.appendChild(groupElement);
		}

		// Delete unused groupElements
		while (childrenIndex < childrenCount) {
			table.removeChild(nodes.getItem(childrenIndex));
			childrenCount--;
		}
	}

	@Override
	public int[] getSizes(IViewRenderContext viewRenderContext,
			Element element, IViewModel modelElement, int[] sizes) {

		Element scrollableContainer = modelElement.getViewModelElements()
				.getScrollableTableContainer();
		if (scrollableContainer != null) {

			if (sizes == null || sizes.length < 2) {
				sizes = new int[2];
			}

			sizes[0] = scrollableContainer.getClientWidth();
			sizes[1] = scrollableContainer.getClientHeight();

			return sizes;
		}

		return super.getSizes(viewRenderContext, element, modelElement, sizes);
	}

	@Override
	public void setFocus(IViewRenderContext viewRenderContext,
			IViewModel viewModel, Element viewElement) {
		if (false) {
			Log.info("ViewRenderer: Set focus to " + viewModel);
		}

		focusElement(viewElement);
	}

	@Override
	public boolean showElementModel(IViewRenderContext viewRenderContext,
			Element viewElement, IViewModel viewModel,
			IElementModel showModelElement) {
		if (super.showElementModel(viewRenderContext, viewElement, viewModel,
				showModelElement)) {
			return true;
		}

		if (showModelElement.getType() == IElementModel.Type.ROW) {
			showRowModel(viewRenderContext, viewModel,
					(IRowModel) showModelElement);

			return true;
		}

		if (showModelElement.getType() == IElementModel.Type.TITLE_CELL) {
			showTitleCellModel(viewRenderContext, viewModel,
					(ITitleCellModel) showModelElement);

			return true;
		}

		return false;
	}

	protected void showRowModel(IViewRenderContext viewRenderContext,
			IViewModel viewModel, IRowModel showRowModel) {

		Element rowElement = viewModel.getViewModelElements().getRowElement(
				showRowModel);
		if (rowElement == null) {
			return;
		}

		showElement(viewRenderContext, rowElement, X_SCROLL_MODE
				| Y_SCROLL_MODE, 1, 1);
	}

	protected void showTitleCellModel(IViewRenderContext viewRenderContext,
			IViewModel viewModel, ITitleCellModel showTitleCellModel) {

		Element titleCellElement = viewModel.getViewModelElements()
				.getTitleCellElement(showTitleCellModel);
		if (titleCellElement == null) {
			return;
		}

		showElement(viewRenderContext, titleCellElement, X_SCROLL_MODE, 0, 0);
	}

	private void showElement(IViewRenderContext viewRenderContext,
			Element element, int scrollMode, int marginTop, int marginLeft) {

		int elementTop = element.getOffsetTop();
		int elementLeft = element.getOffsetLeft();
		int elementWidth = element.getOffsetWidth() + marginLeft * 2; // Marge
		int elementHeight = element.getOffsetHeight() + marginTop * 2; // Marge

		if (elementTop >= marginTop) {
			elementTop -= marginTop;
			elementHeight += marginTop;
		}
		if (elementLeft >= marginLeft) {
			elementLeft -= marginLeft;
			elementWidth += marginLeft;
		}

		IViewModel viewModel = viewRenderContext.getViewModel();

		Element viewElement = viewModel.getViewModelElements().getViewElement();

		int viewLeft = viewModel.getScrollLeft();
		int viewTop = viewModel.getScrollTop();

		int viewSizes[] = getSizes(viewRenderContext, viewElement, viewModel,
				null);
		int viewWidth = viewSizes[0];
		int viewHeight = viewSizes[1];

		int newViewLeft = viewLeft;
		if ((scrollMode & X_SCROLL_MODE) > 0) {

			if (elementLeft <= viewLeft
					&& elementLeft + elementWidth >= viewLeft + viewWidth) {

				// No need to scroll !

			} else if (viewLeft >= elementLeft) {
				newViewLeft = elementLeft;

			} else if (viewLeft + viewWidth < elementLeft + elementWidth) {
				newViewLeft = elementLeft + elementWidth - viewWidth;
			}
		}

		int newViewTop = viewTop;
		if ((scrollMode & Y_SCROLL_MODE) > 0) {

			if (elementTop <= viewTop
					&& elementTop + elementHeight >= viewTop + viewHeight) {
				// No need to scroll !

			} else if (viewTop >= elementTop) {
				newViewTop = elementTop;

			} else if (viewTop + viewHeight < elementTop + elementHeight) {
				newViewTop = elementTop + elementHeight - viewHeight;
			}
		}

		if (false) {
			Log.debug("element=(" + elementLeft + "," + elementTop + " "
					+ elementWidth + "," + elementHeight + ") view=("
					+ viewLeft + "," + viewTop + " " + viewSizes[0] + ","
					+ viewSizes[1] + ") => " + newViewLeft + "," + newViewTop);
		}

		if (newViewLeft != viewLeft || newViewTop != viewTop) {
			setScrollPositions(viewRenderContext, viewElement, viewModel,
					newViewLeft, newViewTop);
		}
	}

	private void setScrollPositions(IViewRenderContext viewRenderContext,
			Element element, IViewModel modelElement, int scrollLeft,
			int scrollTop) {
		Element scrollableContainer = modelElement.getViewModelElements()
				.getScrollableTableContainer();
		if (scrollableContainer != null) {
			scrollableContainer.setScrollLeft(scrollLeft);
			scrollableContainer.setScrollTop(scrollTop);
		}
	}
}
